package com.se.hospitalmonitoring;

import java.awt.EventQueue;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Random;

import com.se.hospitalmonitoring.BedSideInterface.CancelCallListener;
import com.se.hospitalmonitoring.CallButtonInterface.CallButtonListener;
import com.se.hospitalmonitoring.VitalSensorInterface.MockVitalSensor;
import com.se.hospitalmonitoring.VitalSensorInterface.VitalAlarmListener;
import com.se.hospitalmonitoring.VitalSensorInterface.VitalSensorListener;
import com.se.hospitalmonitoring.VitalSign.VitalSignMeasurement;

public class BedSideControl extends Thread implements CallButtonListener,
	VitalAlarmListener, VitalSensorListener, CancelCallListener {
	Socket clientSocket;
	ObjectOutputStream out;
	int port;
	int bedId;
	OutMessageHandler outMessageHandler;
	Message messageToSend = null;

	/**
	 * This class handles out going messages
	 */
	class OutMessageHandler extends Thread {

		/**
		 * Have the thread wait
		 */
		public synchronized void waitForMessage() {
			try {
				wait();
			}
			catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		/**
		 * Notifies the thread
		 */
		public synchronized void newMessageArrived() {
			notifyAll();
		}

		/**
		 * Wait until the new message arrives, then send it out, then go back to
		 * waiting
		 */
		@Override
		public void run() {
			while (true) {
				while (messageToSend == null) {
					waitForMessage();
				}
				try {
					synchronized (this) {
						out.writeObject(messageToSend);
						out.flush();
						messageToSend = null;
					}
				}
				catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * Constructor
	 * 
	 * @param port
	 *            port number to connect to
	 * 
	 * @param ID
	 *            bed ID
	 */
	public BedSideControl(int port, int ID) {
		this.port = port;
		this.bedId = ID;
		VitalSensorInterface vsi = VitalSensorInterface.getInstance();
		vsi.addVitalAlarmListener(this);
		vsi.addVitalSensorListener(this);
		CallButtonInterface.getInstance().addListener(this);
	}

	/**
	 * Called when a new vital sign is received from the interface
	 * 
	 * @param vitalSign
	 *            The vital sign that was received
	 */
	@Override
	public void onNewVitalMeasurement(VitalSignMeasurement vitalSign) {
		sendMessage(new Message(Message.Type.VitalSignUpdate, bedId, vitalSign));
	}

	/**
	 * Called when the call button is pressed
	 */
	@Override
	public void onCallButtonPressed() {
		sendMessage(new Message(Message.Type.CallNotification, bedId,
			new CallNotification()));
	}

	/**
	 * Called when a vital sign measurement triggers an alarm
	 * 
	 * @param vitalSign
	 *            The vital sign that triggered the alarm
	 */
	@Override
	public void onAlarmTriggered(VitalSign vitalSign) {
		sendMessage(new Message(Message.Type.AlarmNotification, bedId,
			new AlarmNotification(vitalSign)));
	}

	/**
	 * Starts the clientSocket
	 */
	@Override
	public synchronized void run() {
		try {
			// TODO: Make the address configurable. Only works locally right now
			clientSocket = new Socket("localhost", port);
			clientSocket.setKeepAlive(true);
			System.out.println("Connection established on "
				+ clientSocket.getInetAddress().getHostName());
			out = new ObjectOutputStream(new BufferedOutputStream(clientSocket
				.getOutputStream()));
			out.writeObject(new Message(Message.Type.Startup, bedId, null));
			out.flush();
			outMessageHandler = new OutMessageHandler();
			outMessageHandler.start();
		}
		catch (UnknownHostException e) {
			e.printStackTrace();
		}
		catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * returns the ID of this bed
	 * 
	 * @return ID of this bed
	 */
	public int getID() {
		return bedId;
	}

	/**
	 * Called when the alarm is reset
	 */
	@Override
	public void onAlarmCanceled() {
		sendMessage(new Message(Message.Type.CancelAlarm, bedId, null));
	}

	/**
	 * Called when the call button is reset
	 */
	@Override
	public void onCallCanceled() {
		sendMessage(new Message(Message.Type.CancelCall, bedId, null));
	}

	/**
	 * Send a message to the nurse station
	 * 
	 * @param message
	 *            message to send
	 */
	public void sendMessage(Message message) {
		if (outMessageHandler != null) {
			messageToSend = message;
			outMessageHandler.newMessageArrived();
			System.out.println("Message " + message + " sent");
		}
	}

	/**
	 * Starts the client program
	 * 
	 * @param args
	 *            Unused
	 */
	public static void main(String args[]) {
		Random random = new Random();
		// TODO: Create a way for the nurse station to generate bed id's. There
		// is a possibility right now that two beds could get the same id.
		int bedID = random.nextInt(10000);
		final BedSideControl bedSideControl = new BedSideControl(5555, bedID);
		bedSideControl.start();
		new MockVitalSensor().start();
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					BedSideInterface frame = new BedSideInterface();
					frame.addCancelCallListener(bedSideControl);
					frame.setVisible(true);
				}
				catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}
}
